
import java.util.ArrayList;
/**
 * Tipos que pueden aparacer en la gramática que después se almacenarán el la tabla de tipos.
 * @author paco
 */
public class Tipo
{

    public static int PARAM = 1,
            LOCAL = 2;

    public static int ERRORD = -1,
            ERRORI = -1,
            ERROR = -1,
            INT = 0,
            DOUBLE = 1,
            BOOLEAN = 2,
            VOID = 3,
            VECTOR = 4,
            PARAMETRO = 5,
            CLASE = 6;

    static public String Tipos[] =
    {
        "entero",
        "real",
        "booleano",
        "void",
        "vector",
        "parametro"
    };

    /**
     * Tipo de la variable (entero, real, booleano, clase... )
     */
    private String tipo;
    /**
     * Tipo de parametro
     */
    private Integer parametro;
    /**
     * Si es un return tipo que devuelve
     */
    private Integer retorno;
    /**
     * Si es un array indica el tamaño de esa dimensión del vector
     */
    private Integer tamanyo;
    /**
     * Tipo al que se está haciendo referencia.
     */
    private Integer tipoBase;
    /**
     * Nombre del tipo en caso de ser una variable, una clase ...
     */
    private String nombre;
    /**
     * En caso de que el tipo sea un clase, en este vector se guardan sus campos y sus metodos.
     */
    private TablaSimbolos atributos;
    /**
     * Indica si el tipo tiene constructor en caso de ser una clase
     */
    private boolean  tieneConstructor; 

    /**
     * Crea un nuevo tipo de dato
     * @param tipo Nombre del tipo
     * @param parametro Indica el tipo de parámetro en caso de serlo
     * @param retorno Indica el valor de retorno si es una función
     * @param tamanyo Indica el tamaño del vector en caso de serlo
     * @param tipoBase  Tipo de dato al que apunta
     */
    public Tipo(String tipo, Integer parametro, Integer retorno, Integer tamanyo, Integer tipoBase)
    {
        this.tipo = tipo;
        this.parametro = parametro;
        this.retorno = retorno;
        this.tamanyo = tamanyo;
        this.tipoBase = tipoBase;
        this.nombre = "";
        this.tieneConstructor = false;
    }

    /**
     * Constructor sobrecargado
     * @param tipo Tipo que se va a añadir (clase, vector, ... ).
     * @param nombre Nombre de la clase o variable.
     */
    public Tipo(String tipo, String nombre )
    {
        this.tipo = tipo;
        this.nombre = nombre;
        this.parametro = null;
        this.retorno = null;
        this.tamanyo = null;
        this.tipoBase = null;
        this.tieneConstructor = false;
    }
    
    /**
     * Indica que la clase tiene constructor
     */
    public void setConstructor()
    {
        this.tieneConstructor = true;
    }
    
    /**
     * Devuelve la tabla de simbolos
     */
    public TablaSimbolos getAtributos()
    {
        if ( atributos == null )
            atributos = new TablaSimbolos();
        return this.atributos;
    }

    /**
     * Obtiene el tipo base de un tipo
     * @return Tipo al que se apunta
     */
    public Integer getTipoBase()
    {
        return this.tipoBase;
    }

    /**
     * Devuelve el parametro al que apunta
     * @return Parametro que es apuntado desde el tipo
     */
    public Integer getParametro()
    {
        return parametro;
    }

    /**
     * Devuelve el tipo de retorno en caso de ser un tipo return
     * @return Tipo de retorno (int, bool o double).
     */
    public Integer getRetorno()
    {
        return retorno;
    }

    /**
     * Devuelve el tamaño de un vector
     * @return Entero con el tamaño de el tipo en caso de ser un vector, null en caso contrario
     */
    public Integer getTamanyo()
    {
        return tamanyo;
    }

    /**
     * Devuelve el tipo que estamos gestionando (vector, clase, entero, ... )
     * @return Tipo de la variable.
     */
    public String getTipo()
    {
        return tipo;
    }

    /**
     * Cambia el parametro de un tipo
     * @param parametro Nuevo tipo de parámetro
     */
    public void setParametro(Integer parametro)
    {
        this.parametro = parametro;
    }

    /**
     * Cambia el tipo de retorno
     * @param retorno Nuevo tipo de retorno
     */
    public void setRetorno(Integer retorno)
    {
        this.retorno = retorno;
    }

    /**
     * Cambia el tamaño de un tipo.
     * @param tamanyo Nuevo tamaño
     */
    public void setTamanyo(Integer tamanyo)
    {
        this.tamanyo = tamanyo;
    }

    /**
     * Cambia el tipo
     * @param tipo Nuevo tipo
     */
    public void setTipo(String tipo)
    {
        this.tipo = tipo;
    }

    /**
     * Cambia el tipo base al que apunta un tipo
     * @param tipoBase Tipo al que apunta
     */
    public void setTipoBase(Integer tipoBase)
    {
        this.tipoBase = tipoBase;
    }

    /**
     * Devuelve el nombre del tipo
     * @return Cadena con el nombre del tipo
     */
    public String getNombre()
    {
        return this.nombre;
    }

    /**
     * Transforma un tipo en formato string a formato entero
     * @param s Tipo en formato string
     * @return Tipo en formato entero (int, double o bool ).
     */
    public Integer getTipo(String s)
    {
        if (s.equals("entero"))
        {
            return Tipo.INT;
        } else if (s.equals("real"))
        {
            return Tipo.DOUBLE;
        } else
        {
            return Tipo.BOOLEAN;
        }
    }

    /**
     * Añade un campo a la variable atributos, sólo se utiliza para las clases
     * @param nombre Nombre del campo a añadir
     * @param tipo Tipo del campos (entero, booleano, ... ).
     * @param isPublic Visibilidad de la variable, public si es cierto y private en caso contrario
     */
    public void addCampo(String nombre, int tipo, boolean isPublic, TablaTipos t)
    {
        if (atributos == null)
        {
            atributos = new TablaSimbolos();
        }
        
        Simbolo s = new Simbolo(nombre, tipo, isPublic);
        if ( isPublic )
            s.setPublic();
        s.setTipo(tipo);
        s.setCampo();
        if ( t.get(tipo).isClass() )
            s.setClase();
        atributos.nuevoSimbolo(s); 
    }

    /**
     * Añade un metodo a la variable atributos, sólo se usa para las clases
     * @param nombre Nombre del metodo.
     * @param tipo Tipo del metodo que se va a añadir (int, double, ... )
     * @param isPublic Indica la visibilidad del metodo, public si es true y false en caso contrario.
     */
    public void addMetodo(String nombre, int tipo, boolean isPublic, int numParam)
    {
        if (atributos == null)
        {
            atributos = new TablaSimbolos();
        }

        Simbolo s = new Simbolo(nombre, tipo, isPublic);
        s.setMetodo();
        s.setNumParametros(numParam);
        
        if ( tipo == -1 && numParam == 0)
        {
            this.setConstructor();
            s.setConstructor();
        }

        atributos.nuevoSimbolo(s);
    }

    /**
     * Comprueba si un tipo e vector
     * @return Cierte si es de tipo vector falso en caso contrario
     */
    public boolean esVector()
    {
        return tipo.equals(Tipos[VECTOR]);
    }

    /**
     * Escribe los metodos y campos que pueda tener un tipo clase
     * @return Cadena con los datos almacenados dentro de la clase
     */
    public String toString( TablaTipos t )
    {
        String salida = "";
        if ( this.isClass() )
        {
            salida += (".class '" + this.getNombre() + "' extends [mscorlib]System.Object {\n");
            if ( !this.tieneConstructor )
                salida += this.escribeConstructor();
        }
        if ( atributos != null && atributos.getSimbolos() != null )
        {
            for (Simbolo s : atributos.getSimbolos())
            {
                if ( s.isConstructor() )
                {
                    salida += s.escribeConstructor();
                }
                else if (s.isCampo())
                {
                    int tb = getTipoBase() == null ? 0 : getTipoBase();
                    salida += s.escribeCampo( t.get(s.getTipo()).getNombre(), s.isArray(), tb);
                } else if ( s.isMetodo() )
                {
                   int n = ( s.getNumParametros() == null ? 0:s.getNumParametros() );
                   
                    salida += s.escribeMetodo(n, s.getLocals());
                }
            }
        }
        if ( this.isClass() )
            salida += "\n}\n";
        return salida;
    }
    
    public String escribeConstructor()
    {
        String s = "";
        s += ".method public specialname rtspecialname instance\n";
        s += "void .ctor () cil managed {\n";
        s += "ldarg 0\n";
        s += "call instance void [mscorlib]System.Object::.ctor()\n";
        s += ".maxstack 1\n";
        s += "ret\n";
        s += "}\n";

        return s;
    }

    /**
     * Indica si un tipo es una clase
     * @return Cierto si el tipo es una clase, falso en caso contrario.
     */
    public boolean isClass()
    {
        return tipo.equals("clase");
    }
}
